package com.dream.utils.hibernate;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;

import javax.annotation.Resource;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.ArrayUtils;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.springframework.orm.hibernate4.HibernateCallback;

import com.dream.utils.log.DreamLogUtils;
import com.dream.utils.system.cfg.SystemConfigUtils;
import com.google.common.collect.Lists;

/**
 * @copyright evan
 * @author evan
 * @Revision
 * @date 2012/9/22
 */
public abstract class DreamBaseDAO<T> {

	@Resource(name = "sessionFactory")
	private SessionFactory sessionFactory;

	private Class<T> type;
	
	private int flushCount = 20;

	public DreamBaseDAO() {

		injectType();
	}
	
	@SuppressWarnings("unchecked")
	protected void injectType() {
		Class<?> c = getClass();
		java.lang.reflect.Type type = c.getGenericSuperclass();
		if (type instanceof ParameterizedType) {
			java.lang.reflect.Type[] parameterizedType = ((ParameterizedType) type).getActualTypeArguments();
			this.type = (Class<T>) parameterizedType[0];
			this.flushCount = SystemConfigUtils.instance().getInteger("system.db.flush.count", 20);
			}
	}
	
	public SessionFactory getSessionFactory(){
		return sessionFactory;
	}
	
	public  Session getCurrentSession(){
		return sessionFactory.getCurrentSession();
	}
	
	public boolean isExist(Serializable id) {
		
		T obj = selectById(id);
		return obj == null ? false : true;
		
	}
	
	@SuppressWarnings("unchecked")
	public T selectById(Serializable id){
		
		Session session = getCurrentSession();
		Object object = session.get(type, id);
		return (T)object;
				
	}
	
	@SuppressWarnings("unchecked")
	public List<T> selectAll(){
		
		Session session = getCurrentSession();
		Criteria criteria = session.createCriteria(type);
		return criteria.list();
		
	}
	
	public List<T> selectAll(boolean isDelete) {
		
		DetachedCriteriaBuilder builder = DetachedCriteriaBuilder.instance(type);
		builder.addEq("deleted", isDelete);
		builder.addAscOrder(getEntityIdName());
		return select(builder);
		
	}
	
	@SuppressWarnings("rawtypes")
	public Long countAll(){
		
		final String entityName = sessionFactory.getClassMetadata(type).getEntityName();
		final String HQL_COUNT_ALL = String.format("select count(*) from %s", entityName);
		Session session = getCurrentSession();
		Query query = session.createQuery(HQL_COUNT_ALL);
		List list = query.list();
		
		return CollectionUtils.isEmpty(list)?0:(Long)list.get(0);
		
	}
	
	public List<T> select(DetachedCriteriaBuilder builder) {
		return selectE(builder);
	}
	
	public <E> List<E> selectE(DetachedCriteriaBuilder builder){
		
		CriteriaSearchCallback<List<E>> callback = new CriteriaSearchCallback<List<E>>();
		callback.setCriteria(builder.getDetachedCriteria()); 
		List<E>	list = Lists.newArrayList();
		try {
			list =  callback.doInHibernate(getCurrentSession());
		} catch (HibernateException e) {
			DreamLogUtils.error(DreamBaseDAO.class, "selectE{criteria}", "", e);
			throw new RuntimeException(e);
		} catch (SQLException e) {
			DreamLogUtils.error(DreamBaseDAO.class, "selectE{criteria}", "", e);
			throw new RuntimeException(e);
		}
		return list;
	}
	
	public <E> List<E> selectE(HibernateCallback<List<E>> callback) {
		
		List<E>	list = Lists.newArrayList();
		try {
			list =  callback.doInHibernate(getCurrentSession());
		} catch (HibernateException e) {
			DreamLogUtils.error(DreamBaseDAO.class, "selectE{callback}", "", e);
			throw new RuntimeException(e);
		} 
		return list;
	}
	
	public T selectTopOne(DetachedCriteriaBuilder builder) {
		 List<T> list = selectTopN(builder,1);
		 return  CollectionUtils.isNotEmpty(list) ? list.get(0) : null;
	}
	
	public Object selectTopOne(String hql, Object[] objs) {
		
		Session session = getCurrentSession();
		Query query = session.createQuery(hql);
		if(ArrayUtils.isNotEmpty(objs)){
			int i = 0;
			for(Object obj : objs){
				query.setParameter(i++, obj);
			}
		}
		@SuppressWarnings("rawtypes")
		List list = query.list();
		return CollectionUtils.isNotEmpty(list) ? list.get(0) : null;
	}
	
	public List<T>  selectTopN(DetachedCriteriaBuilder builder,int n) {
		
		PagingVO param = new PagingVO();
		param.setPageIndex("1");
		param.setPageSize(Integer.toString(n));
		List<T> res = (List<T>) selectPaging(builder,param);
		return res;
	}
	
	public <E> List<E>  selectTopNE(DetachedCriteriaBuilder builder,int n) {
		PagingVO param = new PagingVO();
		param.setPageIndex("1");
		param.setPageSize(Integer.toString(n));
		@SuppressWarnings("unchecked")
		List<E> res = (List<E>) selectPaging(builder,param);
		return res;
	}

	public List<T> selectPaging(DetachedCriteriaBuilder builder, PagingVO page) {
		
		builder.setOrder(page.getOrderName());
		DetachedCriteria criteria = builder.getDetachedCriteria();
		
		PagingCriteriaSearchCallback<List<T>> searchCallback = new PagingCriteriaSearchCallback<List<T>>(criteria);
		searchCallback.setPageIndex(page.getPageIndex()).setPageSize(page.getPageSize());
		
		List<T>	list = Lists.newArrayList();
		try {
			list =  searchCallback.doInHibernate(getCurrentSession());
		} catch (HibernateException e) {
			DreamLogUtils.error(DreamBaseDAO.class, "selectPaging{criteria}", "", e);
			throw new RuntimeException(e);
		} catch (SQLException e) {
			DreamLogUtils.error(DreamBaseDAO.class, "selectPaging{criteria}", "", e);
			throw new RuntimeException(e);
		}
		return list;
	}
	
	public <E> List<E> selectPagingE(DetachedCriteriaBuilder builder, PagingVO page) {

		PagingCriteriaSearchCallback<List<E>> searchCallback = new PagingCriteriaSearchCallback<List<E>>(builder.getDetachedCriteria());
		searchCallback.setPageIndex(page.getPageIndex()).setPageSize(page.getPageSize());
		List<E>	list = Lists.newArrayList();
		try {
			list =  searchCallback.doInHibernate(getCurrentSession());
		} catch (HibernateException e) {
			DreamLogUtils.error(DreamBaseDAO.class, "selectPagingE{criteria}", "", e);
			throw new RuntimeException(e);
		} catch (SQLException e) {
			DreamLogUtils.error(DreamBaseDAO.class, "selectPagingE{criteria}", "", e);
			throw new RuntimeException(e);
		}
		return list;
	}
	
	public PagingVO selectPagingVO(DetachedCriteriaBuilder queryBuilder,PagingVO page,DetachedCriteriaBuilder countBuilder){
		
		
		page.setDetails(selectPaging(queryBuilder,page));
		page.setCount(count(countBuilder));
		return page;
	}
	
	public Long count(DetachedCriteriaBuilder builder) {
		
		builder.setProjection(Projections.rowCount());
		Object count = selectTopOne(builder);
		return (Long)count;
		
	}
	
	public Serializable save(T t){
		checkMutable();
		Session session = getCurrentSession();
		return session.save(t);
	}
	
	public void delete(T t){
		checkMutable();
		Session session = getCurrentSession();
		session.delete(t);
	}
	
	public void deleteById(Serializable id){
		
		checkMutable();
		Session session = getCurrentSession();
		Object object = session.get(type, id);
		session.delete(object);
		
	}
	
	public void update(T t){
		checkMutable();
		Session session = getCurrentSession();
		session.update(t);
	}
	
	public void saveOrUpdate(T t){
		checkMutable();
		Session session = getCurrentSession();
		session.saveOrUpdate(t);
	}
	
	public void merge(T t){
		checkMutable();
		Session session = getCurrentSession();
		session.merge(t);
	}
	
	public void saveOrUpdateBatch(Collection<T> ts) {
		
		saveOrUpdateBatch(ts,flushCount);
	}
	
	public void saveOrUpdateBatch(Collection<T> ts,int flushCount) {
		
		if(CollectionUtils.isNotEmpty(ts)){
			int i=0;
			for(T t : ts){
				this.saveOrUpdate(t);
				if(i>0 && i++ % flushCount == 0){
					getCurrentSession().flush();
				}
			}
		}
	}
	
	public int bulkUpdate(String hql , Object ...objects){
		
		Query query = this.getCurrentSession().createQuery(hql);
		if(ArrayUtils.isNotEmpty(objects)){
			int i = 0;
			for(Object obj : objects){
				query.setParameter(i++, obj);
			}
		}
		return query.executeUpdate();
	}
	
	public void deleteBatch(Collection<T> ts) {
		
		saveOrUpdateBatch(ts,flushCount);
	}

	public void deleteBatch(Collection<T> ts,int flushCount) {

		if(CollectionUtils.isNotEmpty(ts)){
			int i=0;
			for(T t : ts){
				this.delete(t);
				if(i>0 && i++ % flushCount == 0){
					getCurrentSession().flush();
				}
			}
		}
	}
	
	public void flushAndClear(){
		
		Session session = getCurrentSession();
		session.flush();
		session.clear();
		
	}
	
	private void checkMutable() {
		boolean mutable = getSessionFactory().getClassMetadata(this.type).isMutable();

		if (!mutable) {
			throw new RuntimeException("Try to save imutable enity!");
		}
	}
	
	private String getEntityIdName() {
		
		String idName = getSessionFactory().getClassMetadata(this.type).getIdentifierPropertyName();
		return idName;
	}

}
